﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections;

namespace VNTG.UserProfileFilters
{
    /// <summary>
    /// Helper class to query the internal DSML objects.
    /// These objects are used to display the different attributes listed on the EditConnectionFilters.aspx UI page.
    /// </summary>
    public class DSMLHelper
    {
        string TYPENAME_DSML = "Microsoft.Office.Server.UserProfiles.Synchronization.DSML";
        string TYPENAME_DSMLOBJECT = "Microsoft.Office.Server.UserProfiles.Synchronization.DSMLObject";
        string TYPENAME_DSMLATTRIB = "Microsoft.Office.Server.UserProfiles.Synchronization.DSMLAttribute";
        string UPSYNC_ASSEMBLY_PATH = @"C:\Windows\assembly\GAC_MSIL\Microsoft.Office.Server.UserProfiles.Synchronization\14.0.0.0__71e9bce111e9429c\Microsoft.Office.Server.UserProfiles.Synchronization.dll";

        Assembly syncAssembly;
        Type dsmlType;
        Type dsmlObjType;
        Type dsmlAttribType;

        public DSMLHelper()
        {
            syncAssembly = Assembly.LoadFile(UPSYNC_ASSEMBLY_PATH);
            dsmlType = syncAssembly.GetType(TYPENAME_DSML);
            dsmlObjType = syncAssembly.GetType(TYPENAME_DSMLOBJECT);
            dsmlAttribType = syncAssembly.GetType(TYPENAME_DSMLATTRIB);
        }

        /// <summary>
        /// Writes a list of attributes in the connection for the given objectClass to the console.
        /// Can be useful to get an overview of all available attributes to be used in exclusion filters
        /// </summary>
        /// <param name="objectClass">"user" or "group"</param>
        /// <param name="connection">Connection to search the attributes</param>
        public void ListAttributes(Microsoft.Office.Server.UserProfiles.Connection connection, string objectClass)
        {
            var dsml = GetDSML(connection);
            var allAttributes = GetAttributes(dsml, objectClass);

            // Loop over all the attributes and list their properties
            MethodInfo findAttribDefMI = dsmlType.GetMethod("FindAttributeDefinitionById", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            PropertyInfo namePI = dsmlAttribType.GetProperty("Name");
            PropertyInfo syntaxPI = dsmlAttribType.GetProperty("Syntax");
            foreach (KeyValuePair<string, bool> keyValuePair in allAttributes)
            {
                var attributeDefinitionById = findAttribDefMI.Invoke(dsml, new object[] { keyValuePair.Key });
                if (attributeDefinitionById != null)
                {
                    try
                    {
                        var name = namePI.GetValue(attributeDefinitionById, null);
                        var syntax = syntaxPI.GetValue(attributeDefinitionById, null);
                        Console.WriteLine("{0} - {1}", name, syntax);                        
                    }
                    catch (Exception ex)
                    {
                        // Might happen ... log something (or not) :)
                    }
                }
            }
        }

        /// <summary>
        /// Check if the provided attribute in the given objectClass is a numeric type.
        /// </summary>
        /// <param name="connection"></param>
        /// <param name="objectClass"></param>
        /// <param name="attribute"></param>
        /// <returns></returns>
        /// <remarks>
        /// Used to fill out the IsNumeric property of the FilterSetCondition objects.
        /// </remarks>
        public bool IsAttributeNumeric(Microsoft.Office.Server.UserProfiles.Connection connection, string objectClass, string attribute)
        {
            bool isNumeric = false;
            var dsml = GetDSML(connection);
            var allAttributes = GetAttributes(dsml, objectClass);

            // Loop over all the attributes and list their properties
            MethodInfo findAttribDefMI = dsmlType.GetMethod("FindAttributeDefinitionById", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            PropertyInfo namePI = dsmlAttribType.GetProperty("Name");
            PropertyInfo syntaxPI = dsmlAttribType.GetProperty("Syntax");
            foreach (KeyValuePair<string, bool> keyValuePair in allAttributes)
            {
                var attributeDefinitionById = findAttribDefMI.Invoke(dsml, new object[] { keyValuePair.Key });
                if (attributeDefinitionById != null)
                {
                    try
                    {
                        var name = namePI.GetValue(attributeDefinitionById, null) as string;
                        var syntax = syntaxPI.GetValue(attributeDefinitionById, null);
                        if (name.Equals(attribute, StringComparison.OrdinalIgnoreCase))
                        {
                            isNumeric = syntax.ToString().Equals("Integer");
                        }
                    }
                    catch (Exception ex)
                    {
                        // Might happen ... log something :)
                    }
                }
            }

            return isNumeric;
        }

        /// <summary>
        /// Gets a reference to the internal DSML property of the given connection.
        /// </summary>
        /// <param name="connection"></param>
        /// <returns></returns>
        private object GetDSML(Microsoft.Office.Server.UserProfiles.Connection connection)
        {
            // Get a reference to the internal DSML property of the connection object            
            PropertyInfo dsmlPI = connection.GetType().GetProperty("DSML", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            return dsmlPI.GetValue(connection, null);
        }

        /// <summary>
        /// Get all attributes for the given objectClass.
        /// </summary>
        /// <param name="connection"></param>
        /// <param name="objectClass">"user" or "group"</param>
        private Dictionary<string, bool> GetAttributes(object dsml, string objectClass)
        {
            // Get the object definition           
            MethodInfo findObjDefMI = dsmlType.GetMethod("FindObjectDefinition");
            var objectDefinition = findObjDefMI.Invoke(dsml, new object[] { objectClass });

            // Get the AllAttributes field            
            FieldInfo attribFI = dsmlObjType.GetField("AllAttributes", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            return attribFI.GetValue(objectDefinition) as Dictionary<string, bool>;
        }        
    }
}
